home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 2000 August: Tool Chest / Dev.CD Aug 00 TC Disk 2.toast / pc / sample code / quicktime / quicktime vr / vrmakeobject / vrmakeobject.c < prev    next >
Encoding:
Text File  |  2000-06-23  |  37.4 KB  |  1,106 lines

  1. //////////
  2. //
  3. //    File:        VRMakeObject.c
  4. //
  5. //    Contains:    Code for creating a QuickTime VR object movie from a linear QuickTime movie.
  6. //
  7. //    Written by:    Tim Monroe
  8. //                Based on MakeQTVRObject code by Pete Falco and Michael Chen (and others?).
  9. //
  10. //    Copyright:    © 1991-1998 by Apple Computer, Inc., all rights reserved.
  11. //
  12. //    Change History (most recent first):
  13. //
  14. //       <5>         02/01/99    rtm        reworked prompt and filename handling to remove "\p" sequences
  15. //       <4>         09/30/98    rtm        tweaked call to AddMovieResource to create single-fork movies;
  16. //                                    tweaked call to FlattenMovieData to enable FastStart option
  17. //       <3>         22/01/98    rtm        version 2.0 objects working on MacOS and Windows
  18. //       <2>         21/01/98    rtm        version 1.0 objects working on MacOS and Windows
  19. //       <1>         20/01/98    rtm        first file from QTVRObjectAuthoring.c in MakeQTVRObject 1.0b2
  20. //
  21. //    This file contains functions that convert a linear QuickTime movie into a QuickTime VR object movie.
  22. //    Here we can create both version 1.0 and version 2.0 QTVR object movies.
  23. //
  24. //
  25. //    VERSION 2.0 FILE FORMAT
  26. //
  27. //    The definitive source of information about creating QTVR 2.0 object movies is Chapter 3 of the
  28. //    book "Virtual Reality Programming With QuickTime VR 2.0". (This information is also available
  29. //    online, at <http://dev.info.apple.com/dev/techsupport/insidemac/qtvr/qtvrapi-2.html>.) Here is
  30. //    a condensed version of the info in that chapter, as pertains to objects:
  31. //
  32. //    An object movie is a QuickTime movie that contains at least three tracks: a QTVR track, an object
  33. //    track, and an object image track. In addition, a QuickTime VR movie must contain some special user data
  34. //    that specifies the QuickTime VR movie controller. A QuickTime VR movie can also contain other kinds of
  35. //    tracks, such as hot spot image tracks and even sound tracks.
  36. //
  37. //    A QuickTime VR movie contains a single "QTVR track", which maintains a list of the nodes in the
  38. //    movie. Each individual sample in the QTVR track's media contains information about a single node,
  39. //    such as the node's type, ID, and name. Since we are creating a single-node movie here, our
  40. //    QTVR track will contain a single media sample. 
  41. //
  42. //    Every media sample in a QTVR track has the same sample description, whose type is QTVRSampleDescription.
  43. //    The data field of that sample description is a "VR world", an atom container whose child atoms specify
  44. //    information about the nodes in the movie, such as the default node ID and the default imaging properties.
  45. //    We'll spend a good bit of time putting things into the VR world.
  46. //
  47. //    An object movie also contains a single "object track", which contains information specific to the
  48. //    object nodes in a scene. An object track has a media sample for each media sample in the QTVR track.
  49. //    As a result, our object track will have one sample. The QTVRObjectSampleAtom structure defines the media
  50. //    sample data. 
  51. //
  52. //    The actual image data for an object node is contained in an "object image track". The individual
  53. //    frames in that track are various views of the object. There may also be a "hot spot image track" that
  54. //    contains the hot spot images. This sample code does not create hot spot image tracks.
  55. //
  56. //    So, our general strategy, given a linear QuickTime movie, is as follows:
  57. //        (1) Create a new, empty movie. Call this movie the "QTVR movie".
  58. //        (2) Create a QTVR track and its associated media.
  59. //        (3) Create a VR world atom container; this is stored in the sample description for the QTVR track.
  60. //        (4) Create a node information atom container for each node; this is stored as a media sample
  61. //            in the QTVR track.
  62. //        (5) Create an object track and add it to the movie.
  63. //        (6)    Create an object image track by copying the video track from the QuickTime movie to the QTVR movie.
  64. //        (7) Set up track references from the QTVR track to the object track, and from the object track
  65. //            to the object image track.
  66. //        (8) Add a user data item that identifies the QTVR movie controller.
  67. //        (9) Flatten the QTVR movie into the final object movie.
  68. //
  69. //
  70. //    VERSION 1.0 FILE FORMAT
  71. //
  72. //    The definitive source of information about creating QTVR version 1.0 object movies is Technote 1036,
  73. //    "QuickTime VR 1.0 Object Movie File Format" released in March 1996, available online at the address
  74. //    <http://devworld.apple.com/dev/technotes/tn/tn1036.html>. Here is a condensed version of the info
  75. //    in that technote:
  76. //
  77. //    For version 1.0 object movies, the file format is quite simple. A single-node object movie contains
  78. //    an "object video track", an active video track that contains the various views of the object in the
  79. //    movie frames. An object video track is essentially just a standard QuickTime video track and is the
  80. //    same as the version 2 object image tack.
  81. //
  82. //    What distinguishes an object movie from a standard linear QuickTime movie is the manner in which
  83. //    the frames of the video track are displayed to the user. This is determined by a special piece of
  84. //    user data stored in the object movie file, which selects the QuickTime VR movie controller.
  85. //
  86. //    Various display parameters of the object movie (for instance, the default pan angle) are contained in
  87. //    another piece of user data, of type 'NAVG'. The data in this user data item is structured according
  88. //    to the QTVRObjectFileFormat1x0Record structure.
  89. //
  90. //    A QuickTime VR object movie can also contain a movie poster of the object and a movie file preview.
  91. //    A movie poster is a single view of the object that can be used to represent the object. A poster is
  92. //    defined by specifying a time in the object video track. In general, the poster view should be the same
  93. //    as the initial object view specified in the 'NAVG' user data item. A movie file preview is some part
  94. //    of the object movie that is displayed in order to give the user an idea of what's in the entire movie
  95. //    (for instance, in Standard File Package dialog boxes).
  96. //
  97. //    Version 1.0 object movies do not support hot spots.
  98. //
  99. //    So, our general strategy, given a linear QuickTime movie, is as follows:
  100. //        (1) Create a new, empty movie. Call this movie the "QTVR movie".
  101. //        (2)    Create an object video track by copying the video track from the linear movie to the QTVR movie.
  102. //        (3) Add a user data item of type 'NAVG' to the QTVR movie that specifies object parameters.
  103. //        (4)    Add a user data item of type 'ctyp' that identifies the QTVR movie controller.
  104. //        (5) Set the poster time to the desired view of the object.
  105. //        (6) Create a movie file preview and add it to the movie.
  106. //        (7) Flatten the QTVR movie into the final object movie.
  107. //
  108. //
  109. //    NOTES:
  110. //
  111. //    *** (1) ***
  112. //    The routines in this file use lots of hard-coded values. A real-life application would want to elicit the
  113. //    actual values for a specific object movie from the user. (Hey, this is only sample code!)
  114. //
  115. //    *** (2) ***
  116. //    All data in QTAtom structures must be in big-endian format. We use macros like EndianU32_NtoB to convert
  117. //    values into the proper format before inserting them into atoms. See VRObject_CreateVRWorld for some examples.
  118. //    Similarly, data in the version 1.0 'NAVG' user data item must be big-endian.
  119. //
  120. //////////
  121.  
  122. #include "VRMakeObject.h"
  123.  
  124. UInt32                    gVersionToCreate = kQTVRVersion2;        // the version of the file format we create
  125.  
  126.  
  127. //////////
  128. //
  129. // VRObject_PromptUserForMovieFileAndMakeObject
  130. // Let the user select a linear QuickTime movie file, then make a QTVR object movie from it.
  131. //
  132. //////////
  133.  
  134. void VRObject_PromptUserForMovieFileAndMakeObject (void)
  135. {
  136.     SFTypeList                myTypeList;
  137.     StandardFileReply        myReply;
  138.     FSSpec                    myMoovSpec;
  139.     FSSpec                    myDestSpec;
  140.     StringPtr                 myMoviePrompt = QTUtils_ConvertCToPascalString(kObjSaveMoviePrompt);
  141.     StringPtr                 myMovieFileName = QTUtils_ConvertCToPascalString(kObjSaveMovieFileName);
  142.  
  143.     // have the user select a linear QuickTime movie file
  144.     myTypeList[0] = MovieFileType;
  145.  
  146.     StandardGetFilePreview(NULL, 1, myTypeList, &myReply);
  147.     if (!myReply.sfGood)
  148.         return;
  149.     
  150.     myMoovSpec = myReply.sfFile;
  151.  
  152.     // have the user select the name of the new object movie file
  153.     StandardPutFile(myMoviePrompt, myMovieFileName, &myReply);
  154.     if (!myReply.sfGood)
  155.         return;
  156.  
  157.     myDestSpec = myReply.sfFile;
  158.  
  159.     // just do it...
  160.     VRObject_MakeObjectMovie(&myMoovSpec, &myDestSpec, gVersionToCreate);
  161.  
  162.     // ...and let the user know we're done
  163.     DoBeep();
  164.     
  165.     // now clean up after ourselves
  166.     free(myMoviePrompt);
  167.     free(myMovieFileName);
  168. }
  169.  
  170.  
  171. //////////
  172. //
  173. // VRObject_CreateVRWorld
  174. // Create a VR world atom container and add the basic required atoms to it. Also, create a
  175. // node information atom container and add a node header atom to it. Return both atom containers.
  176. //
  177. // The caller is responsible for disposing of the VR world and the node information atom
  178. // (by calling QTDisposeAtomContainer).
  179. //
  180. // This function assumes that the scene described by the VR world contains a single node whose
  181. // type is specified by the theNodeType parameter.
  182. //
  183. //////////
  184.  
  185. OSErr VRObject_CreateVRWorld (QTAtomContainer *theVRWorld, QTAtomContainer *theNodeInfo, OSType theNodeType)
  186. {
  187.     QTAtomContainer            myVRWorld = NULL;
  188.     QTAtomContainer            myNodeInfo = NULL;
  189.     QTVRWorldHeaderAtom        myVRWorldHeaderAtom;
  190.     QTAtom                    myImagingParentAtom;
  191.     QTAtom                    myNodeParentAtom;
  192.     QTAtom                    myNodeAtom;
  193.     QTVRPanoImagingAtom        myPanoImagingAtom;
  194.     QTVRNodeLocationAtom    myNodeLocationAtom;
  195.     QTVRNodeHeaderAtom        myNodeHeaderAtom;
  196.     UInt16                    myIndex;
  197.     OSErr                    myErr = noErr;
  198.  
  199.     //////////
  200.     //
  201.     // create a VR world atom container
  202.     //
  203.     //////////
  204.  
  205.     myErr = QTNewAtomContainer(&myVRWorld);
  206.     if (myErr != noErr)
  207.         goto bail;
  208.  
  209.     //////////
  210.     //
  211.     // add a VR world header atom to the VR world
  212.     //
  213.     //////////
  214.  
  215.     myVRWorldHeaderAtom.majorVersion = EndianU16_NtoB(kQTVRMajorVersion);
  216.     myVRWorldHeaderAtom.minorVersion = EndianU16_NtoB(kQTVRMinorVersion);
  217.  
  218.     // insert the scene name string, if we have one; if not, set nameAtomID to 0
  219.     if (false) {
  220.         Str255                myStr = "\pMy Scene";
  221.         QTAtomID            myID;
  222.         
  223.         myErr = VRObject_AddStr255ToAtomContainer(myVRWorld, kParentAtomIsContainer, myStr, &myID);
  224.         myVRWorldHeaderAtom.nameAtomID = EndianU32_NtoB(myID);
  225.     } else
  226.         myVRWorldHeaderAtom.nameAtomID = EndianU32_NtoB(0L);
  227.     
  228.     myVRWorldHeaderAtom.defaultNodeID = EndianU32_NtoB(kDefaultNodeID);
  229.     myVRWorldHeaderAtom.vrWorldFlags = EndianU32_NtoB(0L);
  230.     myVRWorldHeaderAtom.reserved1 = EndianU32_NtoB(0L);
  231.     myVRWorldHeaderAtom.reserved2 = EndianU32_NtoB(0L);
  232.  
  233.     // add the atom to the atom container (the VR world)
  234.     myErr = QTInsertChild(myVRWorld, kParentAtomIsContainer, kQTVRWorldHeaderAtomType, 1, 1, sizeof(QTVRWorldHeaderAtom), &myVRWorldHeaderAtom, NULL);
  235.     if (myErr != noErr)
  236.         goto bail;
  237.         
  238.     //////////
  239.     //
  240.     // add an imaging parent atom to the VR world and insert imaging atoms into it
  241.     //
  242.     // imaging atoms describe the default imaging characteristics for the different types of nodes in the scene;
  243.     // currently, only the panorama imaging atoms are defined, so we'll include those (even in object movies)
  244.     //
  245.     //////////
  246.     
  247.     myErr = QTInsertChild(myVRWorld, kParentAtomIsContainer, kQTVRImagingParentAtomType, 1, 1, 0, NULL, &myImagingParentAtom);
  248.     if (myErr != noErr)
  249.         goto bail;
  250.         
  251.     // fill in the fields of the panorama imaging atom structure
  252.     myPanoImagingAtom.majorVersion = EndianU16_NtoB(kQTVRMajorVersion);
  253.     myPanoImagingAtom.minorVersion = EndianU16_NtoB(kQTVRMinorVersion);
  254.     myPanoImagingAtom.correction = EndianU32_NtoB(kQTVRFullCorrection);
  255.     myPanoImagingAtom.imagingValidFlags = EndianU32_NtoB(kQTVRValidCorrection | kQTVRValidQuality | kQTVRValidDirectDraw);
  256.     for (myIndex = 0; myIndex < 6; myIndex++)
  257.         myPanoImagingAtom.imagingProperties[myIndex] = EndianU32_NtoB(0L);
  258.     myPanoImagingAtom.reserved1 = EndianU32_NtoB(0L);
  259.     myPanoImagingAtom.reserved2 = EndianU32_NtoB(0L);
  260.     
  261.     // add a panorama imaging atom for kQTVRMotion state
  262.     myPanoImagingAtom.quality = EndianU32_NtoB(codecLowQuality);
  263.     myPanoImagingAtom.directDraw = EndianU32_NtoB(true);
  264.     myPanoImagingAtom.imagingMode = EndianU32_NtoB(kQTVRMotion);
  265.     myErr = QTInsertChild(myVRWorld, myImagingParentAtom, kQTVRPanoImagingAtomType, 0, 0, sizeof(QTVRPanoImagingAtom), &myPanoImagingAtom, NULL);
  266.     if (myErr != noErr)
  267.         goto bail;
  268.         
  269.     // add a panorama imaging atom for kQTVRStatic state
  270.     myPanoImagingAtom.quality = EndianU32_NtoB(codecHighQuality);
  271.     myPanoImagingAtom.directDraw = EndianU32_NtoB(false);
  272.     myPanoImagingAtom.imagingMode = EndianU32_NtoB(kQTVRStatic);
  273.     myErr = QTInsertChild(myVRWorld, myImagingParentAtom, kQTVRPanoImagingAtomType, 0, 0, sizeof(QTVRPanoImagingAtom), &myPanoImagingAtom, NULL);
  274.     if (myErr != noErr)
  275.         goto bail;
  276.         
  277.     //////////
  278.     //
  279.     // add a node parent atom to the VR world and insert node ID atoms into it
  280.     //
  281.     //////////
  282.     
  283.     myErr = QTInsertChild(myVRWorld, kParentAtomIsContainer, kQTVRNodeParentAtomType, 1, 1, 0, NULL, &myNodeParentAtom);
  284.     if (myErr != noErr)
  285.         goto bail;
  286.         
  287.     // add a node ID atom
  288.     myErr = QTInsertChild(myVRWorld, myNodeParentAtom, kQTVRNodeIDAtomType, kDefaultNodeID, 0, 0, 0, &myNodeAtom);
  289.     if (myErr != noErr)
  290.         goto bail;
  291.     
  292.     // add a single node location atom to the node ID atom
  293.     myNodeLocationAtom.majorVersion = EndianU16_NtoB(kQTVRMajorVersion);
  294.     myNodeLocationAtom.minorVersion = EndianU16_NtoB(kQTVRMinorVersion);
  295.     myNodeLocationAtom.nodeType = EndianU32_NtoB(theNodeType);
  296.     myNodeLocationAtom.locationFlags = EndianU32_NtoB(kQTVRSameFile);
  297.     myNodeLocationAtom.locationData = EndianU32_NtoB(0);
  298.     myNodeLocationAtom.reserved1 = EndianU32_NtoB(0);
  299.     myNodeLocationAtom.reserved2 = EndianU32_NtoB(0);
  300.     
  301.     // insert the node location atom into the node ID atom
  302.     myErr = QTInsertChild(myVRWorld, myNodeAtom, kQTVRNodeLocationAtomType, 1, 1, sizeof(QTVRNodeLocationAtom), &myNodeLocationAtom, NULL);
  303.     if (myErr != noErr)
  304.         goto bail;
  305.     
  306.     //////////
  307.     //
  308.     // create a node information atom container and add a node header atom to it
  309.     //
  310.     //////////
  311.     
  312.     myErr = QTNewAtomContainer(&myNodeInfo);
  313.     if (myErr != noErr)
  314.         goto bail;
  315.  
  316.     myNodeHeaderAtom.majorVersion = EndianU16_NtoB(kQTVRMajorVersion);
  317.     myNodeHeaderAtom.minorVersion = EndianU16_NtoB(kQTVRMinorVersion);
  318.     myNodeHeaderAtom.nodeType = EndianU32_NtoB(theNodeType);
  319.     myNodeHeaderAtom.nodeID = EndianU32_NtoB(kDefaultNodeID);
  320.     myNodeHeaderAtom.commentAtomID = EndianU32_NtoB(0L);
  321.     myNodeHeaderAtom.reserved1 = EndianU32_NtoB(0L);
  322.     myNodeHeaderAtom.reserved2 = EndianU32_NtoB(0L);
  323.     
  324.     // insert the node name string into the node info atom container
  325.     if (false) {
  326.         Str255                myStr = "\pMy Node";
  327.         QTAtomID            myID;
  328.         
  329.         myErr = VRObject_AddStr255ToAtomContainer(myNodeInfo, kParentAtomIsContainer, myStr, &myID);
  330.         myNodeHeaderAtom.nameAtomID = EndianU32_NtoB(myID);
  331.     } else
  332.         myNodeHeaderAtom.nameAtomID = EndianU32_NtoB(0L);
  333.     
  334.     // insert the node header atom into the node info atom container
  335.     myErr = QTInsertChild(myNodeInfo, kParentAtomIsContainer, kQTVRNodeHeaderAtomType, 1, 1, sizeof(QTVRNodeHeaderAtom), &myNodeHeaderAtom, NULL);
  336.     if (myErr != noErr)
  337.         goto bail;
  338.     
  339.     //////////
  340.     //
  341.     // create hot spot atoms and add them to the node information atom container
  342.     // [left as an exercise for the reader]
  343.     //
  344.     //////////
  345.     
  346. bail:
  347.     // return the atom containers that we've created and configured here
  348.     *theVRWorld = myVRWorld;
  349.     *theNodeInfo = myNodeInfo;
  350.     
  351.     return(myErr);
  352. }
  353.  
  354.  
  355. //////////
  356. //
  357. // VRObject_CreateObjectTrack
  358. // Configure the specified object track. Note that theSrcMovie is the linear QuickTime movie.
  359. //
  360. //////////
  361.  
  362. OSErr VRObject_CreateObjectTrack (Movie theSrcMovie, Track theObjectTrack, Media theObjectMedia)
  363. {
  364.     SampleDescriptionHandle        mySampleDesc = NULL;
  365.     QTAtomContainer                myObjectSample;
  366.     QTVRObjectSampleAtom        myObjectSampleData;
  367.     TimeValue                    myDuration;
  368.     TimeValue                    myCurrTime;
  369.     Float32                        myInitialPan, myInitialTilt;
  370.     OSErr                        myErr = noErr;
  371.  
  372.     //////////
  373.     //
  374.     // get some information from the linear QuickTime movie
  375.     //
  376.     //////////
  377.     
  378.     // get the duration of a single video frame
  379.     GetMovieNextInterestingTime(theSrcMovie, nextTimeMediaSample, 0, NULL, (TimeValue)0, fixed1, NULL, &myDuration);
  380.  
  381.     // get the movie's current time, and convert it to an initial pan/tilt pair
  382.     myCurrTime = GetMovieTime(theSrcMovie, NULL);
  383.     
  384.     VRObject_GetPanAndTiltFromTime(myCurrTime,
  385.                                     kDefaultFrameDuration,
  386.                                     kDefaultNumOfColumns,
  387.                                     kDefaultNumOfRows,
  388.                                     kDefaultLoopSize,
  389.                                     kDefaultStartPan,
  390.                                     kDefaultEndPan,
  391.                                     kDefaultStartTilt,
  392.                                     kDefaultEndTilt,
  393.                                     &myInitialPan, &myInitialTilt);
  394.  
  395.     //////////
  396.     //
  397.     // add a media sample to the object track
  398.     //
  399.     //////////
  400.     
  401.     // create a sample description; this contains no real info, but AddMediaSample requires it
  402.     mySampleDesc = (SampleDescriptionHandle)NewHandleClear(sizeof(SampleDescription));
  403.  
  404.     myErr = QTNewAtomContainer(&myObjectSample);
  405.     if (myErr != noErr)
  406.         goto bail;
  407.     
  408.     myObjectSampleData.majorVersion = EndianU16_NtoB(kQTVRMajorVersion);
  409.     myObjectSampleData.minorVersion = EndianU16_NtoB(kQTVRMinorVersion);
  410.     
  411.     myObjectSampleData.movieType = EndianU16_NtoB(kDefaultMovieType);
  412.     myObjectSampleData.viewStateCount = EndianU16_NtoB(kDefaultViewStateCount);
  413.     myObjectSampleData.defaultViewState = EndianU16_NtoB(kDefaultDefaultViewState);
  414.     myObjectSampleData.mouseDownViewState = EndianU16_NtoB(kDefaultMouseDownViewState);
  415.     
  416.     myObjectSampleData.viewDuration = EndianU32_NtoB(myDuration);
  417.     myObjectSampleData.columns = EndianU32_NtoB((UInt32)kDefaultNumOfColumns);
  418.     myObjectSampleData.rows = EndianU32_NtoB((UInt32)kDefaultNumOfRows);
  419.     
  420.     myObjectSampleData.mouseMotionScale = kDefaultMouseMotionScale;
  421.     myObjectSampleData.minPan = kDefaultStartPan;
  422.     myObjectSampleData.maxPan = kDefaultEndPan;
  423.     myObjectSampleData.defaultPan = myInitialPan;
  424.     myObjectSampleData.minTilt = kDefaultStartTilt;
  425.     myObjectSampleData.maxTilt = kDefaultEndTilt;
  426.     myObjectSampleData.defaultTilt = myInitialTilt;
  427.     myObjectSampleData.minFieldOfView = kDefaultMinFieldOfView;
  428.     myObjectSampleData.fieldOfView = kDefaultFieldOfView;
  429.     myObjectSampleData.defaultFieldOfView = kDefaultFieldOfView;
  430.     myObjectSampleData.defaultViewCenterH = kDefaultDefaultViewCenterH;
  431.     myObjectSampleData.defaultViewCenterV = kDefaultDefaultViewCenterV;
  432.     myObjectSampleData.viewRate = kDefaultViewRate;
  433.     myObjectSampleData.frameRate = kDefaultFrameRate;
  434.  
  435.     VRObject_ConvertFloatToBigEndian(&myObjectSampleData.mouseMotionScale);
  436.     VRObject_ConvertFloatToBigEndian(&myObjectSampleData.minPan);
  437.     VRObject_ConvertFloatToBigEndian(&myObjectSampleData.maxPan);
  438.     VRObject_ConvertFloatToBigEndian(&myObjectSampleData.defaultPan);
  439.     VRObject_ConvertFloatToBigEndian(&myObjectSampleData.minTilt);
  440.     VRObject_ConvertFloatToBigEndian(&myObjectSampleData.maxTilt);
  441.     VRObject_ConvertFloatToBigEndian(&myObjectSampleData.defaultTilt);
  442.     VRObject_ConvertFloatToBigEndian(&myObjectSampleData.minFieldOfView);
  443.     VRObject_ConvertFloatToBigEndian(&myObjectSampleData.fieldOfView);
  444.     VRObject_ConvertFloatToBigEndian(&myObjectSampleData.defaultFieldOfView);
  445.     VRObject_ConvertFloatToBigEndian(&myObjectSampleData.defaultViewCenterH);
  446.     VRObject_ConvertFloatToBigEndian(&myObjectSampleData.defaultViewCenterV);
  447.     VRObject_ConvertFloatToBigEndian(&myObjectSampleData.viewRate);
  448.     VRObject_ConvertFloatToBigEndian(&myObjectSampleData.frameRate);
  449.  
  450.     myObjectSampleData.animationSettings = EndianU32_NtoB(kDefaultAnimationSettings);
  451.     myObjectSampleData.controlSettings = EndianU32_NtoB(kDefaultControlSettings); 
  452.     
  453.     // insert the object sample atom into the object sample atom container
  454.     myErr = QTInsertChild(myObjectSample, kParentAtomIsContainer, kQTVRObjectInfoAtomType, 1, 1, sizeof(QTVRObjectSampleAtom), &myObjectSampleData, NULL);
  455.     if (myErr != noErr)
  456.         goto bail;
  457.     
  458.     // get the duration of the object image track (which is the same as the duration of the linear video track)
  459.     myDuration = GetMovieDuration(theSrcMovie);
  460.     
  461.     // create the media sample
  462.     BeginMediaEdits(theObjectMedia);
  463.  
  464.     myErr = AddMediaSample(theObjectMedia, (Handle)myObjectSample, 0, GetHandleSize((Handle)myObjectSample), myDuration, (SampleDescriptionHandle)mySampleDesc, 1, 0, NULL);
  465.     if (myErr != noErr)
  466.         goto bail;
  467.  
  468.     EndMediaEdits(theObjectMedia);
  469.  
  470.     // add the media to the track
  471.     myErr = InsertMediaIntoTrack(theObjectTrack, 0, 0, myDuration, fixed1);
  472.     
  473. bail:
  474.     return(myErr);
  475. }
  476.  
  477.  
  478. //////////
  479. //
  480. // VRObject_CreateQTVRMovieVers1x0
  481. // Create a single-node QuickTime VR object movie from the specified QuickTime movie.
  482. //
  483. // NOTE: This function builds a movie that conforms to version 1.0 of the QuickTime VR file format.
  484. //
  485. //////////
  486.  
  487. OSErr VRObject_CreateQTVRMovieVers1x0 (FSSpec *theObjMovSpec, FSSpec *theSrcMovSpec)
  488. {
  489.     QTVRObjectFileFormat1x0Ptr        myObjFormatPtr = NULL;
  490.     Movie                            myObjMovie = NULL;
  491.     Movie                            mySrcMovie = NULL;
  492.      short                            myObjResRefNum = 0;
  493.     short                            mySrcResRefNum = 0;
  494.     short                            myResID = movieInDataForkResID;
  495.     long                            myFlags = createMovieFileDeleteCurFile | createMovieFileDontCreateResFile;
  496.     UserData                        myUserData;
  497.     TimeValue                        myDuration;
  498.     TimeValue                        myCurrTime;
  499.     Float32                            myInitialPan, myInitialTilt;
  500.     Track                            myImageTrack;
  501.     OSErr                            myErr = noErr;
  502.     
  503.     //////////
  504.     //
  505.     // create a new movie
  506.     //
  507.     //////////
  508.  
  509.     // create a movie file for the destination movie
  510.     myErr = CreateMovieFile(theObjMovSpec, FOUR_CHAR_CODE('TVOD'), smCurrentScript, myFlags, &myObjResRefNum, &myObjMovie);
  511.     if (myErr != noErr)
  512.         goto bail;
  513.  
  514.     //////////
  515.     //
  516.     // copy the video track from the linear movie to the new movie; this is the "object video track"
  517.     //
  518.     //////////
  519.  
  520.     // open the source linear movie file
  521.     myErr = OpenMovieFile(theSrcMovSpec, &mySrcResRefNum, fsRdPerm);
  522.     if (myErr != noErr)
  523.         goto bail;
  524.     
  525.     myErr = NewMovieFromFile(&mySrcMovie, mySrcResRefNum, NULL, 0, 0, 0);
  526.     if (myErr != noErr)
  527.         goto bail;
  528.     
  529.     SetMoviePlayHints(mySrcMovie, hintsHighQuality, hintsHighQuality);
  530.  
  531.     // copy the video track from the linear movie to the object movie
  532.     myErr = VRObject_ImportVideoTrack(mySrcMovie, myObjMovie, &myImageTrack);
  533.     if (myErr != noErr)
  534.         goto bail;
  535.     
  536.     //////////
  537.     //
  538.     // get some information from the linear QuickTime movie
  539.     //
  540.     //////////
  541.     
  542.     // get the duration of a single video frame
  543.     GetMovieNextInterestingTime(mySrcMovie, nextTimeMediaSample, 0, NULL, (TimeValue)0, fixed1, NULL, &myDuration);
  544.  
  545.     // get the movie's current time, and convert it to an initial pan/tilt pair
  546.     myCurrTime = GetMovieTime(mySrcMovie, NULL);
  547.     
  548.     VRObject_GetPanAndTiltFromTime(myCurrTime,
  549.                                     kDefaultFrameDuration,
  550.                                     kDefaultNumOfColumns,
  551.                                     kDefaultNumOfRows,
  552.                                     kDefaultLoopSize,
  553.                                     kDefaultStartPan,
  554.                                     kDefaultEndPan,
  555.                                     kDefaultStartTilt,
  556.                                     kDefaultEndTilt,
  557.                                     &myInitialPan, &myInitialTilt);
  558.     //////////
  559.     //
  560.     // add a user data item of type 'NAVG' to the QTVR movie
  561.     //
  562.     //////////
  563.     
  564.     // create an object file format record
  565.      myObjFormatPtr = (QTVRObjectFileFormat1x0Ptr)NewPtrClear(sizeof(QTVRObjectFileFormat1x0Record));
  566.     if (myObjFormatPtr == NULL)
  567.         goto bail;
  568.  
  569.     // fill in the object file format record
  570.     (*myObjFormatPtr).versionNumber = EndianU16_NtoB(1);
  571.     (*myObjFormatPtr).numberOfColumns = EndianU16_NtoB(kDefaultNumOfColumns);
  572.     (*myObjFormatPtr).numberOfRows = EndianU16_NtoB(kDefaultNumOfRows);
  573.     (*myObjFormatPtr).reserved1 = EndianU16_NtoB(0);
  574.     (*myObjFormatPtr).loopSize = EndianU16_NtoB(kDefaultLoopSize);
  575.     (*myObjFormatPtr).frameDuration    = EndianU16_NtoB((short)myDuration);
  576.     (*myObjFormatPtr).movieType = EndianU16_NtoB(kDefaultMovieType);
  577.     (*myObjFormatPtr).loopTicks = EndianU16_NtoB(kDefaultLoopTicks);
  578.     
  579.     (*myObjFormatPtr).fieldOfView = EndianS32_NtoB(FloatToFixed(kDefaultFieldOfView));
  580.     (*myObjFormatPtr).startHPan = EndianS32_NtoB(FloatToFixed(kDefaultStartPan));
  581.     (*myObjFormatPtr).endHPan = EndianS32_NtoB(FloatToFixed(kDefaultEndPan));
  582.     (*myObjFormatPtr).endVPan = EndianS32_NtoB(FloatToFixed(kDefaultEndTilt));
  583.     (*myObjFormatPtr).startVPan = EndianS32_NtoB(FloatToFixed(kDefaultStartTilt));
  584.     (*myObjFormatPtr).initialHPan = EndianS32_NtoB(FloatToFixed(myInitialPan));
  585.     (*myObjFormatPtr).initialVPan = EndianS32_NtoB(FloatToFixed(myInitialTilt));
  586.     (*myObjFormatPtr).reserved2 = EndianU32_NtoB(0L);
  587.     
  588.     // get the movie's user data list
  589.     myUserData = GetMovieUserData(myObjMovie);
  590.     if (myUserData == NULL) {
  591.         myErr = userDataItemNotFound;
  592.         goto bail;
  593.     }
  594.     
  595.     // add the object file format data as a user data item to the movie
  596.     myErr = SetUserDataItem(myUserData, myObjFormatPtr, sizeof(QTVRObjectFileFormat1x0Record), kObjectFormat1x0DataType, 0);
  597.     if (myErr != noErr)
  598.         goto bail;
  599.     
  600.     //////////
  601.     //
  602.     // add a user data item that identifies the QTVR movie controller
  603.     //
  604.     //////////
  605.     
  606.     myErr = VRObject_SetControllerType(myObjMovie, kQTVROldObjectType);
  607.     if (myErr != noErr)
  608.         goto bail;
  609.         
  610.     //////////
  611.     //
  612.     // set the movie's poster time, current time, and preview
  613.     //
  614.     //////////
  615.     
  616.     // set the poster time to the linear movie's current time
  617.     SetMoviePosterTime(myObjMovie, myCurrTime);
  618.  
  619.     // set the movie's current time to the poster view time
  620.     SetMovieTimeValue(myObjMovie, myCurrTime);
  621.  
  622.     // make a movie preview with duration equal to the animation loop
  623.     SetMoviePreviewTime(myObjMovie, myCurrTime, (*myObjFormatPtr).frameDuration * (*myObjFormatPtr).loopSize);
  624.  
  625.     //////////
  626.     //
  627.     // add the movie resource to the object movie
  628.     //
  629.     //////////
  630.     
  631.     myErr = AddMovieResource(myObjMovie, myObjResRefNum, &myResID, NULL);
  632.     
  633. bail:
  634.     if (myObjFormatPtr != NULL)
  635.         DisposePtr((Ptr)myObjFormatPtr);
  636.     
  637.     if (mySrcResRefNum != 0)
  638.         CloseMovieFile(mySrcResRefNum);
  639.     
  640.     if (mySrcMovie != NULL)
  641.         DisposeMovie(mySrcMovie);
  642.         
  643.     if (myObjResRefNum != 0)
  644.         CloseMovieFile(myObjResRefNum);
  645.     
  646.     if (myObjMovie != NULL)
  647.         DisposeMovie(myObjMovie);
  648.         
  649.     return(myErr);
  650. }
  651.  
  652.  
  653. //////////
  654. //
  655. // VRObject_CreateQTVRMovieVers2x0
  656. // Create a single-node QuickTime VR object movie from the specified QuickTime movie.
  657. //
  658. // NOTE: This function builds a movie that conforms to version 2.0 of the QuickTime VR file format.
  659. //
  660. //////////
  661.  
  662. OSErr VRObject_CreateQTVRMovieVers2x0 (FSSpec *theObjMovSpec, FSSpec *theSrcMovSpec)
  663. {
  664.     Handle                            myHandle = NULL;
  665.     SampleDescriptionHandle            mySampleDesc = NULL;
  666.     QTVRSampleDescriptionHandle        myQTVRDesc = NULL;
  667.     QTAtomContainer                    myVRWorld;
  668.     QTAtomContainer                    myNodeInfo;
  669.     Movie                            myObjMovie = NULL;
  670.     Movie                            mySrcMovie = NULL;
  671.      short                            myObjResRefNum = 0;
  672.     short                            mySrcResRefNum = 0;
  673.     short                            myResID = movieInDataForkResID;
  674.     Track                            myQTVRTrack = NULL;
  675.     Media                            myQTVRMedia = NULL;
  676.     Track                            myObjectTrack = NULL;
  677.     Media                            myObjectMedia = NULL;
  678.     Track                            myImageTrack = NULL;
  679.     long                            mySize;
  680.     long                            myFlags = createMovieFileDeleteCurFile | createMovieFileDontCreateResFile;
  681.     TimeValue                        myDuration;
  682.     TimeScale                        myScale;
  683.     Fixed                            myWidth, myHeight;
  684.     OSErr                            myErr = noErr;
  685.     
  686.     //////////
  687.     //
  688.     // create a new movie
  689.     //
  690.     //////////
  691.  
  692.     // create a movie file for the destination movie
  693.     myErr = CreateMovieFile(theObjMovSpec, FOUR_CHAR_CODE('TVOD'), smCurrentScript, myFlags, &myObjResRefNum, &myObjMovie);
  694.     if (myErr != noErr)
  695.         goto bail;
  696.  
  697.     //////////
  698.     //
  699.     // copy the video track from the linear movie to the new movie; this is the "object image track"
  700.     //
  701.     //////////
  702.  
  703.     // open the source linear movie file
  704.     myErr = OpenMovieFile(theSrcMovSpec, &mySrcResRefNum, fsRdPerm);
  705.     if (myErr != noErr)
  706.         goto bail;
  707.     
  708.     myErr = NewMovieFromFile(&mySrcMovie, mySrcResRefNum, NULL, 0, 0, 0);
  709.     if (myErr != noErr)
  710.         goto bail;
  711.     
  712.     SetMoviePlayHints(mySrcMovie, hintsHighQuality, hintsHighQuality);
  713.  
  714.     // copy the video track from the linear movie to the object movie
  715.     myErr = VRObject_ImportVideoTrack(mySrcMovie, myObjMovie, &myImageTrack);
  716.     if (myErr != noErr)
  717.         goto bail;
  718.     
  719.     //////////
  720.     //
  721.     // get some information from the linear QuickTime movie
  722.     //
  723.     //////////
  724.     
  725.     // get the duration and dimensions of the object image track
  726.     myDuration = GetTrackDuration(myImageTrack);
  727.     GetTrackDimensions(myImageTrack, &myWidth, &myHeight);
  728.     myScale = GetMediaTimeScale(GetTrackMedia(myImageTrack));
  729.     
  730.     //////////
  731.     //
  732.     // create the QTVR movie track and media
  733.     //
  734.     //////////
  735.  
  736.     myQTVRTrack = NewMovieTrack(myObjMovie, myWidth, myHeight, kFullVolume);
  737.     myQTVRMedia = NewTrackMedia(myQTVRTrack, kQTVRQTVRType, myScale, NULL, 0);
  738.     if ((myQTVRTrack == NULL) || (myQTVRMedia == NULL))
  739.         goto bail;
  740.         
  741.     // create a VR world atom container and a node information atom container;
  742.     // remember that the VR world becomes part of the QTVR sample description,
  743.     // and the node information atom container becomes the media sample data
  744.     myErr = VRObject_CreateVRWorld(&myVRWorld, &myNodeInfo, kQTVRObjectType);
  745.     if (myErr != noErr)
  746.         goto bail;
  747.         
  748.     if ((myVRWorld == NULL) || (myNodeInfo == NULL))
  749.         goto bail;
  750.     
  751.     // create a QTVR sample description
  752.     mySize = sizeof(QTVRSampleDescription) + GetHandleSize((Handle)myVRWorld) - sizeof(long);
  753.     myQTVRDesc = (QTVRSampleDescriptionHandle)NewHandleClear(mySize);
  754.     if (myQTVRDesc == NULL)
  755.         goto bail;
  756.         
  757.     (**myQTVRDesc).descSize = mySize;
  758.     (**myQTVRDesc).descType = kQTVRQTVRType;
  759.     (**myQTVRDesc).reserved1 = 0;
  760.     (**myQTVRDesc).reserved2 = 0;
  761.     (**myQTVRDesc).dataRefIndex = 0;
  762.  
  763.     // copy the VR world atom container into the data field of the QTVR sample description
  764.     BlockMove(*((Handle)myVRWorld), &((**myQTVRDesc).data), GetHandleSize((Handle)myVRWorld));
  765.     
  766.     // create the media sample
  767.     BeginMediaEdits(myQTVRMedia);
  768.  
  769.     myErr = AddMediaSample(myQTVRMedia, (Handle)myNodeInfo, 0, GetHandleSize((Handle)myNodeInfo), myDuration, (SampleDescriptionHandle)myQTVRDesc, 1, 0, NULL);
  770.     if (myErr != noErr)
  771.         goto bail;
  772.  
  773.     EndMediaEdits(myQTVRMedia);
  774.     
  775.     // add the media to the track
  776.     InsertMediaIntoTrack(myQTVRTrack, 0, 0, myDuration, fixed1);
  777.     
  778.     //////////
  779.     //
  780.     // create an object track and add it to the movie
  781.     //
  782.     //////////
  783.     
  784.     // create object track and media
  785.     myObjectTrack = NewMovieTrack(myObjMovie, myWidth, myHeight, 0);
  786.     myObjectMedia = NewTrackMedia(myObjectTrack, kQTVRObjectType, myScale, NULL, 0);
  787.     if ((myObjectTrack == NULL) || (myObjectMedia == NULL))
  788.         goto bail;
  789.     
  790.     myErr = VRObject_CreateObjectTrack(mySrcMovie, myObjectTrack, myObjectMedia);
  791.     if (myErr != noErr)
  792.         goto bail;
  793.         
  794.     //////////
  795.     //
  796.     // create track references from QTVR track to object track
  797.     // and from the object track to the object image track
  798.     //
  799.     //////////
  800.     
  801.     if (myObjectTrack != NULL)
  802.         AddTrackReference(myQTVRTrack, myObjectTrack, kQTVRObjectType, NULL);
  803.         
  804.     if (myImageTrack != NULL)
  805.         AddTrackReference(myObjectTrack, myImageTrack, kQTVRImageTrackRefType, NULL);
  806.  
  807.     //////////
  808.     //
  809.     // add a user data item that identifies the QTVR movie controller
  810.     //
  811.     //////////
  812.     
  813.     myErr = VRObject_SetControllerType(myObjMovie, kQTVRQTVRType);
  814.     if (myErr != noErr)
  815.         goto bail;
  816.         
  817.     //////////
  818.     //
  819.     // add the movie resource to the object movie
  820.     //
  821.     //////////
  822.     
  823.     myErr = AddMovieResource(myObjMovie, myObjResRefNum, &myResID, NULL);
  824.     
  825. bail:
  826.     if (mySampleDesc != NULL)
  827.         DisposeHandle((Handle)mySampleDesc);
  828.     
  829.     if (myQTVRDesc != NULL)
  830.         DisposeHandle((Handle)myQTVRDesc);
  831.     
  832.     if (myVRWorld != NULL)
  833.         QTDisposeAtomContainer(myVRWorld);
  834.         
  835.     if (myNodeInfo != NULL)
  836.         QTDisposeAtomContainer(myNodeInfo);
  837.  
  838.     if (myObjResRefNum != 0)
  839.         CloseMovieFile(myObjResRefNum);
  840.     
  841.     if (myObjMovie != NULL)
  842.         DisposeMovie(myObjMovie);
  843.         
  844.     if (mySrcResRefNum != 0)
  845.         CloseMovieFile(mySrcResRefNum);
  846.         
  847.     if (mySrcMovie != NULL) 
  848.         DisposeMovie(mySrcMovie);
  849.         
  850.     return(myErr);
  851. }
  852.  
  853.  
  854. //////////
  855. //
  856. // VRObject_MakeObjectMovie
  857. // Create a single-node QuickTime VR object movie from the specified linear QuickTime movie file.
  858. //
  859. //////////
  860.  
  861. OSErr VRObject_MakeObjectMovie (FSSpec *theMovieSpec, FSSpec *theDestSpec, long theVersion)
  862. {
  863.     FSSpec                        myTempSpec;
  864.     Movie                        myTempMovie = NULL;
  865.     Movie                        myObjectMovie = NULL;
  866.     short                        myTempResRefNum = 0;
  867.     OSErr                        myErr = noErr;
  868.  
  869.     // create a temporary version of the object movie file,
  870.     // located in the same directory as the destination object movie file;
  871.     // to create a new file name, we'll just change the last character of the destination movie file name
  872.     // (no doubt you could do a better job here!)
  873.     myTempSpec = *theDestSpec;
  874.     
  875.     if (myTempSpec.name[myTempSpec.name[0]] == 't')
  876.         myTempSpec.name[myTempSpec.name[0]] = '@';
  877.     else
  878.         myTempSpec.name[myTempSpec.name[0]] = 't';
  879.     
  880.     // create a single node object movie in the temp file
  881.     if (theVersion == kQTVRVersion1)
  882.         myErr = VRObject_CreateQTVRMovieVers1x0(&myTempSpec, theMovieSpec);
  883.     else if (theVersion == kQTVRVersion2)
  884.         myErr = VRObject_CreateQTVRMovieVers2x0(&myTempSpec, theMovieSpec);
  885.         
  886.     if (myErr != noErr)
  887.         goto bail;
  888.  
  889.     // create the final, flattened movie
  890.     myErr = OpenMovieFile(&myTempSpec, &myTempResRefNum, fsRdPerm);
  891.     if (myErr != noErr)
  892.         goto bail;
  893.                 
  894.     myErr = NewMovieFromFile(&myTempMovie, myTempResRefNum, NULL, 0, 0, 0);
  895.     if (myErr != noErr)
  896.         goto bail;
  897.                 
  898.     // flatten the temporary file into a new movie file;
  899.     // put the movie resource first so that FastStart is possible
  900.     myObjectMovie = FlattenMovieData(myTempMovie, flattenDontInterleaveFlatten | flattenAddMovieToDataFork | flattenForceMovieResourceBeforeMovieData, theDestSpec, FOUR_CHAR_CODE('TVOD'), smSystemScript, createMovieFileDeleteCurFile | createMovieFileDontCreateResFile);
  901.  
  902. bail:    
  903.     if (myObjectMovie != NULL)
  904.         DisposeMovie(myObjectMovie);
  905.  
  906.     if (myTempMovie != NULL)
  907.         DisposeMovie(myTempMovie);
  908.         
  909.     if (myTempResRefNum != 0)
  910.         CloseMovieFile(myTempResRefNum);
  911.         
  912.     DeleteMovieFile(&myTempSpec);
  913.                     
  914.     return(myErr);
  915. }
  916.  
  917.  
  918. //////////
  919. //
  920. // VRObject_ImportVideoTrack
  921. // Copy a video track from one movie (the source) to another (the destination).
  922. //
  923. //////////
  924.  
  925. OSErr VRObject_ImportVideoTrack (Movie theSrcMovie, Movie theDstMovie, Track *theImageTrack)
  926. {
  927.     Track            mySrcTrack = NULL;
  928.     Media            mySrcMedia = NULL;
  929.     Track            myDstTrack = NULL;
  930.     Media            myDstMedia = NULL;
  931.     Fixed            myWidth, myHeight;
  932.     OSType            myType;
  933.     OSErr            myErr = noErr;
  934.     
  935.     ClearMoviesStickyError();
  936.     
  937.     // get the first video track in the source movie
  938.     mySrcTrack = GetMovieIndTrackType(theSrcMovie, 1, VideoMediaType, movieTrackMediaType);
  939.     if (mySrcTrack == NULL)
  940.         return(paramErr);
  941.     
  942.     // get the track's media and dimensions    
  943.     mySrcMedia = GetTrackMedia(mySrcTrack);
  944.     GetTrackDimensions(mySrcTrack, &myWidth, &myHeight);
  945.     
  946.     // create a destination track
  947.     myDstTrack = NewMovieTrack(theDstMovie, myWidth, myHeight, GetTrackVolume(mySrcTrack));
  948.  
  949.     // create a destination media
  950.     GetMediaHandlerDescription(mySrcMedia, &myType, 0, 0);
  951.     myDstMedia = NewTrackMedia(myDstTrack, myType, GetMediaTimeScale(mySrcMedia), 0, 0);
  952.         
  953.     // copy the entire track
  954.     InsertTrackSegment(mySrcTrack, myDstTrack, 0, GetTrackDuration(mySrcTrack), 0);
  955.     CopyTrackSettings(mySrcTrack, myDstTrack);
  956.     SetTrackLayer(myDstTrack, GetTrackLayer(mySrcTrack));
  957.  
  958.     // an object video track should always be enabled
  959.     SetTrackEnabled(myDstTrack, true);
  960.  
  961.     if (theImageTrack != NULL)
  962.         *theImageTrack = myDstTrack;
  963.  
  964.     return(GetMoviesStickyError());
  965. }
  966.  
  967.  
  968. //////////
  969. //
  970. // VRObject_GetPanAndTiltFromTime
  971. // Get the pan and tilt angles that correspond to the specified movie time.
  972. //
  973. //////////
  974.  
  975. OSErr VRObject_GetPanAndTiltFromTime (TimeValue theTime,
  976.                                         TimeValue theFrameDuration,
  977.                                         short theNumColumns,
  978.                                         short theNumRows,
  979.                                         short theLoopSize,
  980.                                         Float32 theStartPan,
  981.                                         Float32 theEndPan,
  982.                                         Float32 theStartTilt,
  983.                                         Float32 theEndTilt,
  984.                                         Float32 *thePan, 
  985.                                         Float32 *theTilt)
  986. {
  987.     short            myRow, myColumn;
  988.     TimeValue        myTime;
  989.     Float32            myPanRange;
  990.     Float32            myTiltRange;
  991.     OSErr            myErr = noErr;
  992.     
  993.     myPanRange = theEndPan - theStartPan;
  994.     myTiltRange = theStartTilt - theEndTilt;
  995.  
  996.     theTime /= theFrameDuration;                // adjust for frame duration
  997.     
  998.     myTime = theTime / theLoopSize;
  999.     myRow = myTime / theNumColumns;
  1000.     myColumn = myTime % theNumColumns;
  1001.     
  1002.     // note the mixed Float32 and integer math
  1003.     if (theNumColumns == 1)
  1004.         *thePan = theStartPan;
  1005.     else if (myPanRange == 360.0)
  1006.         *thePan = theStartPan + (myColumn * (myPanRange / (theNumColumns)));
  1007.     else
  1008.         *thePan = theStartPan + (myColumn * (myPanRange / (theNumColumns - 1)));
  1009.     
  1010.     if (theNumRows == 1)
  1011.         *theTilt = theStartTilt;
  1012.     else
  1013.         *theTilt = theStartTilt - (myRow * (myTiltRange / (theNumRows - 1)));
  1014.     
  1015.     return(myErr);
  1016. }
  1017.  
  1018.  
  1019. //////////
  1020. //
  1021. // VRObject_SetControllerType
  1022. // Set the controller type of the specified movie.
  1023. //
  1024. // This function adds an item to the movie's user data;
  1025. // the updated user data is written to the movie file when the movie is next updated
  1026. // (by calling AddMovieResource or UpdateMovieResource).
  1027. //
  1028. //////////
  1029.  
  1030. OSErr VRObject_SetControllerType (Movie theMovie, OSType theType)
  1031. {
  1032.     UserData        myUserData;
  1033.     OSErr            myErr = noErr;
  1034.  
  1035.     // make sure we've got a movie
  1036.     if (theMovie == NULL)
  1037.         return(paramErr);
  1038.         
  1039.     // get the movie's user data list
  1040.     myUserData = GetMovieUserData(theMovie);
  1041.     if (myUserData == NULL)
  1042.         return(paramErr);
  1043.     
  1044.     theType = EndianU32_NtoB(theType);
  1045.     myErr = SetUserDataItem(myUserData, &theType, sizeof(theType), kQTControllerType, 0);
  1046.  
  1047.     return(myErr);
  1048. }
  1049.  
  1050.  
  1051. //////////
  1052. //
  1053. // VRObject_AddStr255ToAtomContainer
  1054. // Add a Pascal string to the specified atom container; return (through theID) the ID of the new string atom.
  1055. //
  1056. //////////
  1057.  
  1058. OSErr VRObject_AddStr255ToAtomContainer (QTAtomContainer theContainer, QTAtom theParent, Str255 theString, QTAtomID *theID)
  1059. {
  1060.     OSErr                    myErr = noErr;
  1061.  
  1062.     *theID = 0;                // initialize the returned atom ID
  1063.     
  1064.     if ((theContainer == NULL) || (theParent == 0))
  1065.         return(paramErr);
  1066.         
  1067.     if (theString[0] != 0) {
  1068.         QTAtom                myStringAtom;
  1069.         UInt16                mySize;
  1070.         QTVRStringAtomPtr    myStringAtomPtr = NULL;
  1071.         
  1072.         mySize = sizeof(QTVRStringAtom) - 4 + theString[0] + 1;
  1073.         myStringAtomPtr = (QTVRStringAtomPtr)NewPtrClear(mySize);
  1074.         
  1075.         if (myStringAtomPtr != NULL) {
  1076.             myStringAtomPtr->stringUsage = EndianU16_NtoB(1);
  1077.             myStringAtomPtr->stringLength = EndianU16_NtoB(theString[0]);
  1078.             BlockMove(theString + 1, myStringAtomPtr->theString, theString[0]);
  1079.             myStringAtomPtr->theString[theString[0]] = '\0';
  1080.             myErr = QTInsertChild(theContainer, theParent, kQTVRStringAtomType, 0, 0, mySize, (Ptr)myStringAtomPtr, &myStringAtom);
  1081.             DisposePtr((Ptr)myStringAtomPtr);
  1082.             
  1083.             if (myErr == noErr)
  1084.                 QTGetAtomTypeAndID(theContainer, myStringAtom, NULL, theID);
  1085.         }
  1086.     }
  1087.     
  1088.     return(myErr);
  1089. }
  1090.  
  1091.  
  1092. //////////
  1093. //
  1094. // VRObject_ConvertFloatToBigEndian
  1095. // Convert the specified floating-point number to big-endian format.
  1096. //
  1097. //////////
  1098.  
  1099. void VRObject_ConvertFloatToBigEndian (float *theFloat)
  1100. {
  1101.     unsigned long        *myLongPtr;
  1102.     
  1103.     myLongPtr = (unsigned long *)theFloat;
  1104.     *myLongPtr = EndianU32_NtoB(*myLongPtr);
  1105. }
  1106.